package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/astaxie/beego/config"
	"github.com/mitchellh/mapstructure"
	"hash/crc32"
	"io/ioutil"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

type (
	BatchAddClient struct {
		ClientNum    int    `field:"客户机数量"  valid:"Max(999)"`                                 //批量添加使用
		StartNum     string `field:"起始编号"  valid:"MinSize(1);MaxSize(3)"`                     //批量添加使用
		StartIp      string `field:"起始Ip"  valid:"Ip"`                                        //批量添加使用
		StartMachine string `field:"机器名前缀"  valid:"Machine;MinSize(1);MaxSize(12);Sensitive"` //批量添加使用

		Gateway string
		Mask string
		DNS1 string
		DNS2 string
	}

	Client struct {
		Mac string
		IPAddr string
		Name string
		MachineAlias string
		MacAlias string
		Gateway string
		Mask string
		DNS1 string
		DNS2 string
	}
)


func main() {
	defer func() {
		if err:=recover();err!= nil {
			fmt.Println(err)
		}
	}()

	var (
		req BatchAddClient
		clients []Client
		content []byte
	)

	req=BatchAddClient{
		ClientNum:    1,
		StartNum:     "1",
		StartIp:      "192.168.1.1",
		StartMachine: "client",
		Gateway:      "192.168.1.1",
		Mask:         "192.168.1.1",
		DNS1:         "192.168.1.1",
		DNS2:         "192.168.1.1",
	}

	_=loadConfig("config.in1i",&req)
	_=loadTerminal(&req)

	//最大编号限制
	startNum, _ := strconv.Atoi(req.StartNum)
	if startNum+req.ClientNum > 1000 {
		checkErr(fmt.Errorf("客户机名称编号超出范围，请修改为001-%d之间。", 999))
	}

	//启始编号不足三位自动补0
	fullStartNum := 3 //填充的编号位数
	startNameZeroNum := fullStartNum - len(req.StartNum)
	for i := 0; i < startNameZeroNum; i++ {
		req.StartNum = "0" + req.StartNum
	}

	//添加客户机
	for i := 0; i < req.ClientNum; i++ {

		client := Client{
			Mac:          "",
			IPAddr:       Long2Ip(Ip2Long(req.StartIp) + uint32(i)),
			Name:         req.StartMachine + StringIncrease(req.StartNum, i),
			MachineAlias: "",
			MacAlias:     "",

			Gateway:      req.Gateway,
			Mask:         req.Mask,
			DNS1:         req.DNS1,
			DNS2:         req.DNS2,
		}

		client.MacAlias=createMacAlias(client.Name)

		clients = append(clients, client)

	}

	if len(clients) > 0 {
		content=NewOncUtil().SetPks("Mac").Slice2Onc(clients)
		checkErr(ioutil.WriteFile("clients.onc",content,os.ModePerm))

		fmt.Printf("生成终端%d台",len(clients))
	}else{
		fmt.Println("无终端生成")
	}

}

func checkErr(err error)  {
	if err!= nil {
		panic(err)
	}
}

func createMacAlias(name string) string {
	n:=crc32.ChecksumIEEE([]byte(name))
	s:=fmt.Sprintf("%X",n)

	if len(s)!= 8 {
		return ""
	}
	return fmt.Sprintf("52-%s-56-%s-%s-%s",s[0:2],s[2:4],s[4:6],s[6:8])
}


//载入配置文件
func loadConfig(path string,conf *BatchAddClient) (err error) {
	var (
		iniconf config.Configer
	)

	if iniconf, err = config.NewConfig("ini", path);err != nil {
		return
	}

	if conf!= nil {
		conf.ClientNum=iniconf.DefaultInt("ClientNum",0)
		conf.StartNum=iniconf.DefaultString("StartNum","")
		conf.StartIp=iniconf.DefaultString("StartIp","")
		conf.StartMachine=iniconf.DefaultString("StartMachine","")
		conf.Gateway=iniconf.DefaultString("Gateway","")
		conf.Mask=iniconf.DefaultString("Mask","")
		conf.DNS1=iniconf.DefaultString("DNS1","")
		conf.DNS2=iniconf.DefaultString("DNS2","")
	}

	return
}

//从终端载入配置
func loadTerminal(conf *BatchAddClient) (err error) {
	fmt.Printf("请输入 终端数量,默认值为:%d\n",conf.ClientNum)
	fmt.Scanf("%d", &conf.ClientNum)

	fmt.Printf("请输入 终端名前缀,默认值为:%s\n",conf.StartMachine)
	fmt.Scanf("%s", &conf.StartMachine)

	fmt.Printf("请输入 终端起始编号,默认值为:%s\n",conf.StartNum)
	fmt.Scanf("%s", &conf.StartNum)

	fmt.Printf("请输入 起始Ip,默认值为:%s\n",conf.StartIp)
	fmt.Scanf("%s", &conf.StartIp)

	fmt.Printf("请输入 网关,默认值为:%s\n",conf.Gateway)
	fmt.Scanf("%s", &conf.Gateway)

	fmt.Printf("请输入 子网掩码,默认值为:%s\n",conf.Mask)
	fmt.Scanf("%s", &conf.Mask)

	fmt.Printf("请输入 DNS1,默认值为:%s\n",conf.DNS1)
	fmt.Scanf("%s", &conf.DNS1)

	fmt.Printf("请输入 DNS2,默认值为:%s\n",conf.DNS2)
	fmt.Scanf("%s", &conf.DNS2)

	return
}


func Long2Ip(ip uint32) string {
	return fmt.Sprintf("%d.%d.%d.%d", ip>>24, ip<<8>>24, ip<<16>>24, ip<<24>>24)
}

func Ip2Long(ipstr string) (ip uint32) {
	r := `^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})`
	reg, err := regexp.Compile(r)
	if err != nil {
		return
	}
	ips := reg.FindStringSubmatch(ipstr)
	if ips == nil {
		return
	}

	ip1, _ := strconv.Atoi(ips[1])
	ip2, _ := strconv.Atoi(ips[2])
	ip3, _ := strconv.Atoi(ips[3])
	ip4, _ := strconv.Atoi(ips[4])

	if ip1 > 255 || ip2 > 255 || ip3 > 255 || ip4 > 255 {
		return
	}

	ip += uint32(ip1 * 0x1000000)
	ip += uint32(ip2 * 0x10000)
	ip += uint32(ip3 * 0x100)
	ip += uint32(ip4)

	return
}

//字符串自增函数
func StringIncrease(s string, sep int) string {
	if s == "" {
		return ""
	}

	sl := len(s)

	s = strings.TrimLeft(s, "0")

	sint, err := strconv.Atoi(s)
	if err != nil {
		return ""
	}

	sint += sep

	ns := strconv.Itoa(sint)
	nsl := len(ns)
	if sl > nsl {
		//新字符串长度小于旧字符串长度
		//左边补0
		var s0 string
		for i := 0; i < (sl - nsl); i++ {
			s0 += "0"
		}

		return s0 + ns
	}

	return ns
}





//onc辅助工具
type OncUtil struct {
	pk []string //标识

	pkFlag       string //主键字段分割符
	mustField    [2]string
	careFields   []string //设置关心的字段,长度为0表示全部导出
	noCareFields []string //设置不关心字段
}

func NewOncUtil(careFields ...string) *OncUtil {
	return &OncUtil{
		pkFlag:       "_",
		careFields:   careFields,
		noCareFields: []string{"XXX_NoUnkeyedLiteral", "XXX_unrecognized", "XXX_sizecache"},
	}
}

//设置关心的字段
func (this *OncUtil) SetNoCareFields(fields ...string) *OncUtil {
	this.noCareFields = append(this.noCareFields, fields...)
	return this
}

//设置关心的字段
func (this *OncUtil) SetPks(pks ...string) *OncUtil {
	this.pk = pks
	return this
}

//定义单条记录中必须满足的字段条件
func (this *OncUtil) SetMustField(key string, value interface{}) *OncUtil {
	this.mustField[0] = key
	this.mustField[1] = fmt.Sprintf("%v", value)
	return this
}

func (this *OncUtil) pkString() string {
	return strings.Join(this.pk, this.pkFlag)
}

//传入onc内容解析成[]map
func (this *OncUtil) Onc2Map(data []byte) (res []map[string]string) {

	rows := strings.Split(string(data), "[")
	if len(rows) > 0 {
		rows = rows[1:]
	}

	for _, row := range rows {
		item := make(map[string]string)
		if rowArr := strings.Split(row, "\n"); len(rowArr) > 0 {
			for k, field := range rowArr {
				field = strings.TrimRight(field, "\r")
				if k == 0 {
					if len(this.pk) == 1 {
						//多主键,不进行解析
						item[this.pk[0]] = strings.TrimRight(field, "]")
					}

				} else {
					if fieldArr := strings.Split(field, "="); len(fieldArr) >= 2 {

						if !this.canExport(fieldArr[0]) {
							continue
						}

						item[fieldArr[0]] = fieldArr[1]
					}
				}
			}
		}

		//必要条件判定
		if this.mustField[0] == "" || (this.mustField[0] != "" && this.mustField[1] == item[this.mustField[0]]) {

			if len(this.pk) == 1 {
				if item[this.pkString()] != "" {
					res = append(res, item)
				}
			} else {
				res = append(res, item)

			}

		}
	}

	return
}

//[]map解析成Onc文件格式
func (this *OncUtil) Map2Onc(m []map[string]interface{}) (data []byte) {
	var buf bytes.Buffer

	getPkVal := func(itemMap map[string]interface{}) (pkVal string) {
		for _, v := range this.pk {

			if res, ok := itemMap[v]; ok {
				pkVal += fmt.Sprintf("%+v%s", res, this.pkFlag)
			}
		}

		return strings.TrimRight(pkVal, this.pkFlag)
	}

	getSortMapKeys := func(itemMap map[string]interface{}) (keys []string) {
		for k := range itemMap {
			keys = append(keys, k)
		}

		sort.Strings(keys)
		return
	}

	for _, row := range m {
		//加入必要条件

		if this.mustField[0] != "" {
			row[this.mustField[0]] = this.mustField[1]
		}

		//写入主键
		//pkVal := getPkVal(row)
		////if pkVal == "" {
		////	//主键为空不导出
		////	continue
		////}
		pk := fmt.Sprintf("[%s]\n", getPkVal(row))
		buf.Write([]byte(pk))

		keys := getSortMapKeys(row)
		for _, field := range keys {
			if len(this.pk) == 1 && this.inArr(field, this.pk) {
				continue
			}

			if !this.canExport(field) {
				continue
			}
			//写入字段
			fieldVal := fmt.Sprintf("%s=%v\n", field, row[field])
			buf.Write([]byte(fieldVal))
		}

	}

	return buf.Bytes()
}

//arrMap2Slice
func (this *OncUtil) Onc2Slice(data []byte, s interface{}) {

	var (
		m []map[string]string
	)

	if m = this.Onc2Map(data); len(m) >= 0 {
		_ = mapstructure.WeakDecode(m, s)
	}

	return
}

//切片转[]byte
func (this *OncUtil) Slice2Onc(s interface{}) (data []byte) {
	var (
		m       []map[string]interface{}
		content []byte
	)

	if content, _ = json.Marshal(s); len(content) > 0 {
		//避免科学计数法出现
		d := json.NewDecoder(bytes.NewReader(content))
		d.UseNumber()
		//结构体转map
		if _ = d.Decode(&m); len(m) > 0 {
			data = this.Map2Onc(m)

		}
	}

	return
}

func (this *OncUtil) canExport(field string) bool {
	if this.mustField[0] != "" && this.mustField[0] == field {
		return true
	}

	if this.inArr(field, this.noCareFields) {
		return false
	}

	if len(this.careFields) > 0 && !this.inArr(field, this.careFields) {
		return false
	}

	return true

}

func (this *OncUtil) inArr(s string, ss []string) bool {
	for _, v := range ss {
		if s == v {
			return true
		}
	}

	return false
}
